/*
 * jira.c - cve-check-tool
 *
 * Copyright (C) 2015 Intel Corporation
 *
 * cve-check-tool is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 */

#define _GNU_SOURCE
#include "config.h"

#include <curl/curl.h>
#include <glib-object.h>
#include <glib/gprintf.h>
#include <json-glib/json-glib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "jira.h"

/** JIRA-applicable cve-check-tool config file parameters */
struct jira_cfg_t {
        gchar *url;
        gchar *user;
        gchar *password;
        int timeout_secs;
        bool verbose;
        GSList *fields;
        gchar *jql;
        gchar *search_filter;
        GHashTable *template;
};

/** User specified fields in JIRA parsed in the cve-check-tool config file */
struct jira_cfg_field_t {
        gchar *name;
        gchar *attribute;
        gchar *value;
        bool is_array;
};

/** JIRA server send/receive data structure */
struct jira_data_t {
        gchar *data;
        size_t size;
};

/** Helper fuction to compose authentication string for JIRA server */
static bool get_jira_secret(gchar **secret);

/** Helper fuction to return string length */
static size_t get_str_length(const gchar *str);

/** Helper fuction to determine if string is NULL or empty */
static bool is_empty(const gchar *str);

/** Helper fuction to determine if plugin has been initialized */
static bool is_initialized(void);

/** Parses cve-check-tool cfg file JIRA field */
static bool parse_jira_cfg_field(const gchar *key, const gchar *value);

/** Parses cve-check-tool cfg file bool values */
static bool parse_jira_cfg_bool(const gchar *path, GKeyFile *key_file, const gchar *group, const gchar *key,
                                bool *assignee, bool ignore_error, bool default_value);

/** Parses cve-check-tool cfg file int values */
static bool parse_jira_cfg_int(const gchar *path, GKeyFile *key_file, const gchar *group, const gchar *key,
                               int *assignee, bool ignore_error, int default_value);

/** Parses cve-check-tool cfg file */
static bool parse_jira_cfg_file(GKeyFile *config, const gchar *path);

/** Parses cve-check-tool cfg file str values */
static bool parse_jira_cfg_str(const gchar *path, GKeyFile *keyfile, const gchar *group, const gchar *key,
                               gchar **assignee, bool ignore_error, gchar *default_value);

/** Curl callback function for server writes */
static size_t write_jira_cb(void *ptr, size_t size, size_t memb, void *data);

/** Instance to hold configuration file parameter values */
static struct jira_cfg_t *g_jira_cfg = NULL;

bool add_new_jira_issue(const gchar *jira_json)
{
        bool ret = false;
        gchar *url = NULL;
        CURL *curl = NULL;
        CURLcode curl_status = CURLE_OK;
        autofree(gchar) *secret = NULL;
        struct curl_slist *headers = NULL;
        struct jira_data_t post;
        struct jira_data_t response;

        if (!is_initialized()) {
                return false;
        }
        if (is_empty(jira_json)) {
                fprintf(stderr, "Error: Missing json to add new JIRA issue\n");
                return false;
        }
        if (is_empty(g_jira_cfg->url)) {
                fprintf(stderr, "Error: Missing require JIRA server URL\n");
                return false;
        }
        if (!get_jira_secret(&secret)) {
                return false;
        }
        url = g_strdup_printf("%s/issue", g_jira_cfg->url);
        if (url == NULL) {
                curl_global_cleanup();
                fprintf(stderr, "Error: Out of memory for searching issues\n");
                return false;
        }
        response.data = g_malloc(1);
        if (response.data == NULL) {
                curl_global_cleanup();
                fprintf(stderr, "Error: Out of memory\n");
                return false;
        }
        response.size = 0;
        post.data = g_strdup(jira_json);
        post.size = (long)get_str_length(jira_json);
        curl = curl_easy_init();
        if (!curl) {
                curl_global_cleanup();
                fprintf(stderr, "Error: Curl failed to initalize for adding a new issue\n");
                return false;
        }
        headers = curl_slist_append(NULL, "Accept: application/json");
        headers = curl_slist_append(headers, "Content-Type: application/json");
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
        curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcurl-agent/1.0");
        curl_easy_setopt(curl, CURLOPT_URL, url);
        curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L);
        curl_easy_setopt(curl, CURLOPT_USERPWD, secret);
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post.data);
        curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, post.size);
        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_jira_cb);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&response);
        curl_easy_setopt(curl, CURLOPT_READDATA, &post);
        if (g_jira_cfg->timeout_secs) {
                curl_easy_setopt(curl, CURLOPT_TIMEOUT, g_jira_cfg->timeout_secs);
        }
        if (g_jira_cfg->verbose) {
                curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
                fprintf(stderr, "%s\n", post.data);
        }
        curl_status = curl_easy_perform(curl);
        if (curl_status != CURLE_OK) {
                fprintf(stderr, "Error: %s\n", curl_easy_strerror(curl_status));
                ret = false;
        } else {
                if (g_jira_cfg->verbose) {
                        fprintf(stderr, "%s\n", post.data);
                }
                ret = check_jira_response(response.data);
                if (ret) {
                        /* Perhaps a double check with new jql query here */
                }
        }
        g_free(response.data);
        g_free(post.data);
        curl_easy_cleanup(curl);
        curl_global_cleanup();
        return ret;
}

bool build_new_jira_issue(const gchar *summary, const gchar *description, bool as_template, gchar **jira_json)
{
        const GSList *iter = NULL;
        struct jira_cfg_field_t *jira_cfg_field = NULL;
        autofree(gchar) *json_new_issue = NULL;

        if (!is_initialized()) {
                return false;
        }
        iter = g_jira_cfg->fields;
        if (is_empty(summary) && !as_template) {
                fprintf(stderr, "Error: Missing value for summary\n");
                return false;
        }
        if (is_empty(description) && !as_template) {
                fprintf(stderr, "Error: Missing value for description\n");
                return false;
        }
        json_new_issue = g_strdup("{\"fields\":{");
        while (iter) {
                jira_cfg_field = (struct jira_cfg_field_t *)(iter->data);
                if (jira_cfg_field->value != NULL) {
                        if (jira_cfg_field->attribute != NULL) {
                                if (!jira_cfg_field->is_array) {
                                        if (as_template) {
                                                json_new_issue = g_strdup_printf("%s\"%s\":{\"%s\":\"{{%s}}\"},",
                                                                                 json_new_issue,
                                                                                 jira_cfg_field->name,
                                                                                 jira_cfg_field->attribute,
                                                                                 jira_cfg_field->name);
                                        } else {
                                                json_new_issue = g_strdup_printf("%s\"%s\":{\"%s\":\"%s\"},",
                                                                                 json_new_issue,
                                                                                 jira_cfg_field->name,
                                                                                 jira_cfg_field->attribute,
                                                                                 jira_cfg_field->value);
                                        }
                                } else {
                                        if (as_template) {
                                                json_new_issue = g_strdup_printf("%s\"%s\":[{\"%s\":\"{{%s}}\"}],",
                                                                                 json_new_issue,
                                                                                 jira_cfg_field->name,
                                                                                 jira_cfg_field->attribute,
                                                                                 jira_cfg_field->name);
                                        } else {
                                                json_new_issue = g_strdup_printf("%s\"%s\":[{\"%s\":\"%s\"}],",
                                                                                 json_new_issue,
                                                                                 jira_cfg_field->name,
                                                                                 jira_cfg_field->attribute,
                                                                                 jira_cfg_field->value);
                                        }
                                }
                        } else {
                                if (as_template) {
                                        json_new_issue = g_strdup_printf("%s\"%s\":\"{{%s}}\",",
                                                                         json_new_issue,
                                                                         jira_cfg_field->name,
                                                                         jira_cfg_field->name);
                                } else {
                                        json_new_issue = g_strdup_printf("%s\"%s\":\"%s\",",
                                                                         json_new_issue,
                                                                         jira_cfg_field->name,
                                                                         jira_cfg_field->value);
                                }
                        }
                }
                iter = iter->next;
        }
        if (g_str_has_suffix(json_new_issue, ",")) {
                json_new_issue =
                    g_string_erase(g_string_new(json_new_issue), get_str_length(json_new_issue) - 1, 1)->str;
        }
        if (as_template) {
                json_new_issue = g_strdup_printf("%s,\"summary\":\"{{summary}}\",\"description\":\"{{description}}\"",
                                                 json_new_issue);
        } else {
                json_new_issue = g_strdup_printf("%s,\"summary\":\"%s\",\"description\":\"%s\"",
                                                 json_new_issue,
                                                 summary,
                                                 description);
        }
        *jira_json = g_strdup_printf("%s}}", json_new_issue);
        return true;
}

bool build_new_jira_issue_file(const gchar *path, const gchar *summary, const gchar *description, gchar **jira_json)
{
        autofree(GError) *error = NULL;
        autofree(gchar) *data = NULL;
        autofree(gchar) *summary_tmp = g_strdup(summary);
        autofree(gchar) *description_tmp = g_strdup(description);
        autofree(cve_string) *j_json = NULL;

        if (!is_initialized()) {
                return false;
        }
        if (!g_file_get_contents(path, &data, NULL, &error)) {
                fprintf(stderr, "Error: Can't read file. %s\n", error->message);
                return false;
        }
        if (summary_tmp != NULL) {
                g_hash_table_insert(g_jira_cfg->template, "summary", summary_tmp);
        }
        if (description_tmp != NULL) {
                g_hash_table_insert(g_jira_cfg->template, "description", summary_tmp);
        }
        j_json = template_string(data, g_jira_cfg->template);
        *jira_json = g_strdup(j_json->str);
        if (summary_tmp != NULL) {
                g_hash_table_remove(g_jira_cfg->template, "summary");
        }
        if (description_tmp != NULL) {
                g_hash_table_remove(g_jira_cfg->template, "description");
        }
        if (is_empty(*jira_json)) {
                fprintf(stderr, "Error: Template translation failed\n");
                return false;
        }
        return true;
}

bool build_search_jira_issues(gchar **jira_json)
{
        if (!is_initialized()) {
                return false;
        }
        *jira_json = g_strdup_printf("{\"jql\":%s}", g_jira_cfg->jql);
        return true;
}

bool check_jira_response(const gchar *jira_json)
{
        bool ret = false;
        autofree(GError) *error = NULL;
        JsonParser *parser = NULL;
        JsonNode *node, *status = NULL;
        JsonArray *records = NULL;

        if (!is_initialized()) {
                return false;
        }
        if (is_empty(jira_json)) {
                fprintf(stderr, "Error: JIRA returned nothing\n");
                return false;
        }
        parser = json_parser_new();
        json_parser_load_from_data(parser, jira_json, -1, &error);
        if (error) {
                fprintf(stderr, "Error: %s\n", error->message);
                return false;
        }
        node = json_parser_get_root(parser);
        status = json_object_get_member(json_node_get_object(node), "errorMessages");
        if (status != NULL) {
                if (JSON_NODE_HOLDS_ARRAY(status)) {
                        records = json_node_get_array(status);
                        if (json_array_get_length(records)) {
                                fprintf(stderr, "Error: %s\n", json_array_get_string_element(records, 0));
                        } else {
                                fprintf(stderr, "Error: %s\n", jira_json);
                        }
                } else {
                        fprintf(stderr, "Error: JIRA responded with an unexpected message\n");
                }
        } else {
                ret = true;
        }
        json_node_free(node);
        return ret;
}

static void destroy_template(__attribute__((unused)) gpointer key, __attribute__((unused)) gpointer value,
                             gpointer data)
{
        g_free(data);
}

void destroy_jira_plugin(void)
{
        const GSList *iter = NULL;
        struct jira_cfg_field_t *jira_cfg_field = NULL;

        if (g_jira_cfg == NULL) {
                return;
        }
        g_free(g_jira_cfg->url);
        g_free(g_jira_cfg->user);
        g_free(g_jira_cfg->password);
        g_free(g_jira_cfg->jql);
        g_free(g_jira_cfg->search_filter);
        iter = g_jira_cfg->fields;
        while (iter) {
                jira_cfg_field = (struct jira_cfg_field_t *)(iter->data);
                g_free(jira_cfg_field->name);
                g_free(jira_cfg_field->attribute);
                g_free(jira_cfg_field->value);
                iter = iter->next;
        }
        g_hash_table_foreach(g_jira_cfg->template, destroy_template, NULL);
        g_hash_table_destroy(g_jira_cfg->template);
        g_slice_free(struct jira_cfg_t, g_jira_cfg);
        g_jira_cfg = NULL;
}

void free_jira_issues(GSList **jira_issues)
{
        const GSList *iter = NULL;
        struct jira_issue_t *jira_issue = NULL;

        if (jira_issues == NULL || *jira_issues == NULL) {
                return;
        }
        iter = *jira_issues;
        while (iter) {
                jira_issue = iter->data;
                g_free(jira_issue->key);
                g_free(jira_issue->summary);
                g_free(jira_issue->status);
                g_free(jira_issue->description);
                g_slice_free(struct jira_issue_t, jira_issue);
                iter = iter->next;
        }
        g_slist_free(*jira_issues);
        *jira_issues = NULL;
}

struct jira_issue_t *get_jira_issue(const GSList *jira_issues, const gchar *summary)
{
        const GSList *iter = NULL;
        struct jira_issue_t *jira_issue = NULL;

        if (is_empty(summary)) {
                return NULL;
        }
        if (!get_jira_issues_count(jira_issues)) {
                return NULL;
        }
        iter = jira_issues;
        while (iter) {
                jira_issue = iter->data;
                if (!g_strcmp0(jira_issue->summary, summary)) {
                        return jira_issue;
                }
                iter = iter->next;
        }
        return NULL;
}

bool get_jira_issues(const gchar *jira_json, GSList **jira_issues)
{
        autofree(gchar) *jira_issues_json = NULL;
        GSList *issues = NULL;

        if (!is_initialized()) {
                return false;
        }
        if (is_empty(jira_json)) {
                fprintf(stderr, "Error: Missing search JIRA json string\n");
                return false;
        }
        if (!search_jira_issues(jira_json, &jira_issues_json)) {
                return false;
        }
        if (!parse_jira_issues(jira_issues_json, &issues)) {
                return false;
        }
        *jira_issues = issues;
        return true;
}

int get_jira_issues_count(const GSList *jira_issues)
{
        int jira_issues_count = 0;
        const GSList *iter = jira_issues;

        while (iter) {
                ++jira_issues_count;
                iter = iter->next;
        }
        return jira_issues_count;
}

bool get_jira_issues_file(const gchar *path, GSList **jira_issues)
{
        autofree(GError) *error = NULL;
        autofree(gchar) *jira_issues_json = NULL;
        GSList *issues = NULL;

        if (!is_initialized()) {
                return false;
        }
        if (is_empty(path)) {
                fprintf(stderr, "Error: Missing JIRA issues file path\n");
                return false;
        }
        if (!g_file_get_contents(path, &jira_issues_json, NULL, &error)) {
                fprintf(stderr, "Error: Can't read file. %s\n", error->message);
                return false;
        }
        if (!parse_jira_issues(jira_issues_json, &issues)) {
                return false;
        }
        *jira_issues = issues;
        return true;
}

static bool get_jira_secret(gchar **secret)
{
        autofree(gchar) *str = NULL;

        if (is_empty(g_jira_cfg->user)) {
                fprintf(stderr, "Error: Missing required JIRA user name\n");
                return false;
        }
        if (!is_empty(g_jira_cfg->password)) {
                str = g_strdup_printf("%s:%s", g_jira_cfg->user, g_jira_cfg->password);
        } else {
                str = g_strdup_printf("%s", g_jira_cfg->user);
        }
        if (is_empty(str)) {
                fprintf(stderr, "Error: Unable to create an authentication token\n");
                return false;
        }
        *secret = g_strdup_printf("%s", str);
        return true;
}

static size_t get_str_length(const gchar *str)
{
        autofree(cve_string) *s = NULL;
        if (str == NULL) {
                return 0;
        }
        s = cve_string_dup(str);
        return s->len;
}

bool init_jira_plugin(GKeyFile *config, const gchar *path)
{
        destroy_jira_plugin();
        return (parse_jira_cfg_file(config, path));
}

static bool is_empty(const gchar *str)
{
        return (str == NULL || !get_str_length(str));
}

static bool is_initialized()
{
        if (g_jira_cfg == NULL) {
                fprintf(stderr, "Error: JIRA plugin is not initialized\n");
                return false;
        }
        return true;
}

bool is_jira_alive(void)
{
        bool ret = false;
        CURL *curl = NULL;
        CURLcode curl_status = CURLE_OK;
        struct curl_slist *headers = NULL;
        struct jira_data_t response;

        if (!is_initialized()) {
                return false;
        }
        if (is_empty(g_jira_cfg->url)) {
                fprintf(stderr, "Error: Missing require JIRA server URL\n");
                return false;
        }
        curl = curl_easy_init();
        if (!curl) {
                curl_global_cleanup();
                fprintf(stderr, "Error: Curl failed to initialize for searching issues\n");
                return false;
        }
        fprintf(stderr,
                "Checking JIRA server %s with timeout set at %i sec(s)\n",
                g_jira_cfg->url,
                g_jira_cfg->timeout_secs);
        response.data = g_malloc(1);
        if (response.data == NULL) {
                curl_global_cleanup();
                fprintf(stderr, "Error: Out of memory\n");
                return false;
        }
        response.size = 0;
        headers = curl_slist_append(NULL, "Accept: application/json");
        headers = curl_slist_append(headers, "Content-Type: application/json");
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
        curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcurl-agent/1.0");
        curl_easy_setopt(curl, CURLOPT_URL, g_jira_cfg->url);
        curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L);
        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_jira_cb);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&response);
        if (g_jira_cfg->timeout_secs) {
                curl_easy_setopt(curl, CURLOPT_TIMEOUT, g_jira_cfg->timeout_secs);
        }
        if (g_jira_cfg->verbose) {
                curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
        }
        curl_status = curl_easy_perform(curl);
        if (curl_status != CURLE_OK) {
                fprintf(stderr, "Error: %s\n", curl_easy_strerror(curl_status));
                ret = false;
        } else {
                fprintf(stderr, "JIRA server is responsive\n");
                ret = true;
        }
        g_free(response.data);
        curl_easy_cleanup(curl);
        curl_global_cleanup();
        return ret;
}

static bool parse_jira_cfg_bool(const gchar *path, GKeyFile *key_file, const gchar *group, const gchar *key,
                                bool *assignee, bool ignore_error, bool default_value)
{
        autofree(GError) *error = NULL;
        *assignee = g_key_file_get_boolean(key_file, group, key, &error);

        if (error) {
                if (!ignore_error) {
                        fprintf(stderr, "Error: Parsing JIRA %s %s value (%s): %s\n", group, key, error->message, path);
                        return false;
                } else {
                        *assignee = default_value;
                }
        }
        return true;
}

static bool parse_jira_cfg_field(const gchar *key, const gchar *value)
{
        int token_cnt, i = 0;
        struct jira_cfg_field_t *jira_cfg_field = NULL;
        gchar **tokens = NULL;

        tokens = g_strsplit(key, ".", -1);
        token_cnt = g_strv_length(tokens);
        if (tokens == NULL || token_cnt < 1 || token_cnt > 2) {
                fprintf(stderr, "Error: Badly formed JIRA field name %s\n", key);
                return false;
        }
        jira_cfg_field = g_slice_new(struct jira_cfg_field_t);
        if (g_jira_cfg == NULL) {
                fprintf(stderr, "Error: Out of memory\n");
                return false;
        }
        for (i = 0; i < token_cnt; i++) {
                tokens[i] = g_strstrip(tokens[i]);
        }
        jira_cfg_field->name = g_strdup_printf("%s", tokens[0]);
        if (!g_strcmp0(jira_cfg_field->name, "summary")) {
                fprintf(stderr, "Error: The reserved field 'summary' is not allowed in cfg file\n");
                return false;
        }
        if (!g_strcmp0(jira_cfg_field->name, "description")) {
                fprintf(stderr, "Error: The reserved field 'description' is not allowed in cfg file\n");
                return false;
        }
        if ((token_cnt > 1) && (g_str_has_prefix(tokens[1], "[")) && (g_str_has_suffix(tokens[1], "]"))) {
                jira_cfg_field->is_array = true;
        } else {
                jira_cfg_field->is_array = false;
        }
        if (token_cnt == 2) {
                if (!jira_cfg_field->is_array) {
                        jira_cfg_field->attribute = g_strdup_printf("%s", tokens[1]);
                } else {
                        jira_cfg_field->attribute =
                            g_string_erase(g_string_erase(g_string_new(tokens[1]), get_str_length(tokens[1]) - 1, 1),
                                           0,
                                           1)
                                ->str;
                }
        } else {
                jira_cfg_field->attribute = NULL;
        }
        if (is_empty(value)) {
                jira_cfg_field->value = NULL;
                g_hash_table_insert(g_jira_cfg->template, jira_cfg_field->name, "");
        } else {
                jira_cfg_field->value = g_strdup(value);
                g_hash_table_insert(g_jira_cfg->template, jira_cfg_field->name, g_strdup(value));
        }
        g_jira_cfg->fields = g_slist_append(g_jira_cfg->fields, jira_cfg_field);
        g_strfreev(tokens);
        return true;
}

static bool parse_jira_cfg_file(GKeyFile *config, const gchar *path)
{
        autofree(GKeyFile) *key_file = g_key_file_new();
        GKeyFileFlags flags = G_KEY_FILE_KEEP_TRANSLATIONS;
        autofree(GError) *error = NULL;
        autofree(gchar) *value = NULL;
        gchar **keys, **key = NULL;
        gchar *group = NULL;

        if (config == NULL && path == NULL) {
                fprintf(stderr, "Error: Missing configuration key file and/or path\n");
                return false;
        }
        g_jira_cfg = g_slice_new(struct jira_cfg_t);
        if (g_jira_cfg == NULL) {
                fprintf(stderr, "Error: Out of memory\n");
                return false;
        }
        if (config != NULL) {
                key_file = config;
        } else {
                if (!g_key_file_load_from_file(key_file, path, flags, &error)) {
                        fprintf(stderr, "Error: Reading JIRA file: %s\n", error->message);
                        return false;
                }
        }
        group = "JIRA";
        if (!parse_jira_cfg_str(path, key_file, group, "url", &g_jira_cfg->url, false, NULL)) {
                return false;
        }
        if (!parse_jira_cfg_str(path, key_file, group, "user", &g_jira_cfg->user, false, NULL)) {
                return false;
        }
        if (!parse_jira_cfg_str(path, key_file, group, "password", &g_jira_cfg->password, false, NULL)) {
                return false;
        }
        if (!parse_jira_cfg_int(path, key_file, group, "timeout_secs", &g_jira_cfg->timeout_secs, false, 0)) {
                return false;
        }
        if (!parse_jira_cfg_bool(path, key_file, group, "verbose", &g_jira_cfg->verbose, false, false)) {
                return false;
        }
        group = "JIRA-New-Issue";
        g_jira_cfg->template = g_hash_table_new(g_str_hash, g_str_equal);
        keys = g_key_file_get_keys(key_file, group, NULL, &error);
        if (error) {
                fprintf(stderr, "Error: %s\n", error->message);
                return 1;
        }
        if (keys == NULL) {
                fprintf(stderr, "Error: No JIRA json fields found.\n");
                return false;
        }
        key = keys;
        while (*key) {
                value = g_key_file_get_string(key_file, group, *key, &error);
                if (error) {
                        g_strfreev(keys);
                        fprintf(stderr, "Error: %s\n", error->message);
                        return false;
                }
                *key = g_strstrip(*key);
                if (value != NULL) {
                        value = g_strstrip(value);
                }
                if (!parse_jira_cfg_field(*key, value)) {
                        g_strfreev(keys);
                        return false;
                }
                ++key;
        }
        g_strfreev(keys);
        group = "JIRA-Search-Issues";
        if (!parse_jira_cfg_str(path, key_file, group, "jql", &g_jira_cfg->jql, false, NULL)) {
                return false;
        }
        if (!parse_jira_cfg_str(path, key_file, group, "search_filter", &g_jira_cfg->search_filter, false, NULL)) {
                return false;
        }
        return true;
}

static bool parse_jira_cfg_int(const gchar *path, GKeyFile *key_file, const gchar *group, const gchar *key,
                               int *assignee, bool ignore_error, int default_value)
{
        autofree(GError) *error = NULL;

        *assignee = g_key_file_get_integer(key_file, group, key, &error);
        if (error) {
                if (!ignore_error) {
                        fprintf(stderr, "Error: Parsing JIRA %s %s value (%s): %s\n", group, key, error->message, path);
                        return false;
                } else {
                        *assignee = default_value;
                }
        } else if (0 > *assignee) {
                if (!ignore_error) {
                        fprintf(stderr, "Error: Parsing JIRA %s %s value can't be negative: %s\n", group, key, path);
                        return false;
                }
        }
        return true;
}

static bool parse_jira_cfg_str(const gchar *path, GKeyFile *key_file, const gchar *group, const gchar *key,
                               gchar **assignee, bool ignore_error, gchar *default_value)
{
        autofree(GError) *error = NULL;
        *assignee = g_key_file_get_string(key_file, group, key, &error);
        if (error) {
                if (!ignore_error) {
                        fprintf(stderr, "Error: Parsing JIRA %s %s value (%s): %s\n", group, key, error->message, path);
                        return false;
                }
        }
        if (*assignee == NULL) {
                *assignee = g_strdup(default_value);
        }
        return true;
}

bool parse_jira_issues(const gchar *jira_issues_json, GSList **jira_issues)
{
        int issues_cnt, i = 0;
        struct jira_issue_t *jira_issue = NULL;
        autofree(GError) *error = NULL;
        JsonParser *parser = NULL;
        JsonNode *node, *key, *summary, *description, *status, *resolution = NULL;
        JsonObject *object, *field, *record = NULL;
        JsonArray *records = NULL;

        if (!is_initialized()) {
                return false;
        }
        if (is_empty(g_jira_cfg->search_filter)) {
                fprintf(stderr, "Error:  You must specify a search filter\n");
        }
        if (!check_jira_response(jira_issues_json)) {
                return false;
        }
        parser = json_parser_new();
        json_parser_load_from_data(parser, jira_issues_json, -1, &error);
        if (error) {
                fprintf(stderr, "Error: %s\n", error->message);
                return false;
        }
        node = json_object_get_member(json_node_get_object(json_parser_get_root(parser)), "issues");
        if (node == NULL || !JSON_NODE_HOLDS_ARRAY(node)) {
                fprintf(stderr, "Error: Can't find issues field from JIRA response\n");
                return false;
        }
        records = json_node_get_array(node);
        issues_cnt = json_array_get_length(records);
        if (!issues_cnt) {
                fprintf(stderr, "Error: No issues returned from JIRA\n");
                return false;
        }
        for (i = 0; i < issues_cnt; i++) {
                record = json_array_get_object_element(records, i);
                node = json_object_get_member(record, "fields");
                object = json_node_get_object(node);
                summary = json_object_get_member(object, "summary");
                if ((is_empty(g_jira_cfg->search_filter)) ||
                    (g_pattern_match_simple(g_jira_cfg->search_filter, json_node_get_string(summary)))) {
                        jira_issue = g_slice_new(struct jira_issue_t);
                        description = json_object_get_member(object, "description");
                        key = json_object_get_member(record, "key");
                        node = json_object_get_member(object, "status");
                        field = json_node_get_object(node);
                        status = json_object_get_member(field, "name");
                        jira_issue->key = g_strdup(json_node_get_string(key));
                        jira_issue->summary = g_strdup(json_node_get_string(summary));
                        jira_issue->description = g_strdup(json_node_get_string(description));
                        jira_issue->status = g_strdup(json_node_get_string(status));
                        node = json_object_get_member(object, "resolution");
                        if (json_node_is_null(node)) {
                                jira_issue->resolution = g_strdup("");
                        } else {
                                field = json_node_get_object(node);
                                resolution = json_object_get_member(field, "name");
                                jira_issue->resolution = g_strdup(json_node_get_string(resolution));
                        }
                        *jira_issues = g_slist_append(*jira_issues, jira_issue);
                }
        }
        if (!g_slist_length(*jira_issues)) {
                fprintf(stderr, "Error: No matching JIRA issues found with filter: %s\n", g_jira_cfg->search_filter);
                return false;
        }
        return true;
}

bool save(const gchar *something, const gchar *path)
{
        FILE *fp = NULL;

        if (is_empty(something)) {
                fprintf(stderr, "Error: Missing something to save\n");
                return false;
        }
        if (is_empty(path)) {
                fprintf(stderr, "Error: Missing path to save something\n");
                return false;
        }
        fp = fopen(path, "wb");
        if (fp == NULL) {
                fprintf(stderr, "Error: Can't write something to file: %s", path);
                return false;
        }
        fprintf(fp, "%s", something);
        fclose(fp);
        return true;
}

bool save_jira_issues_csv(const GSList *jira_issues, const gchar *path)
{
        const GSList *iter = jira_issues;
        struct jira_issue_t *jira_issue = NULL;
        FILE *fp = NULL;
        int jira_issues_count = get_jira_issues_count(jira_issues);

        if (!jira_issues_count) {
                fprintf(stderr, "Error: No JIRA issues found\n");
                return false;
        }
        if (is_empty(path)) {
                fprintf(stderr, "Error: Missing value for CSV path\n");
                return false;
        }
        fp = fopen(path, "wb");
        if (fp == NULL) {
                fprintf(stderr, "Error: Can't open file for CSV writing: %s\n", path);
                return false;
        }
        fprintf(fp, "JIRA Key,Summary,Status,Description,Resolution\n");
        while (iter) {
                jira_issue = iter->data;
                fprintf(fp,
                        "%s,%s,%s,\"%s\",\"%s\"\n",
                        jira_issue->key,
                        jira_issue->summary,
                        jira_issue->status,
                        jira_issue->description,
                        jira_issue->resolution);
                iter = iter->next;
        }
        fclose(fp);
        return true;
}

bool save_jira_issues_xml(const GSList *jira_issues, const gchar *path)
{
        const GSList *iter = jira_issues;
        struct jira_issue_t *jira_issue = NULL;
        const gchar *sp = "    ";
        FILE *fp = NULL;
        int jira_issues_count = get_jira_issues_count(jira_issues);

        if (!jira_issues_count) {
                fprintf(stderr, "Error: No JIRA issues found\n");
                return false;
        }
        if (is_empty(path)) {
                fprintf(stderr, "Error: Missing value for XML path\n");
                return false;
        }
        fp = fopen(path, "wb");
        if (fp == NULL) {
                fprintf(stderr, "Error: Can't open file for XML writing: %s\n", path);
                return false;
        }
        fprintf(fp, "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?> \n");
        fprintf(fp, "<jira_issues count=\"%i\">\n", jira_issues_count);
        while (iter) {
                jira_issue = iter->data;
                fprintf(fp, "%s<jira_issue>\n", sp);
                fprintf(fp, "%s%s<key>%s</key>\n", sp, sp, jira_issue->key);
                fprintf(fp, "%s%s<summary>%s</summary>\n", sp, sp, jira_issue->summary);
                fprintf(fp, "%s%s<status>%s</status>\n", sp, sp, jira_issue->status);
                fprintf(fp, "%s%s<resolution>%s</resolution>\n", sp, sp, jira_issue->resolution);
                fprintf(fp, "%s%s<description>%s</description>\n", sp, sp, jira_issue->description);
                fprintf(fp, "%s</jira_issue>\n", sp);
                iter = iter->next;
        }
        fprintf(fp, "</jira_issues>\n");
        fclose(fp);
        return true;
}

bool search_jira_issues(const gchar *jira_json, gchar **jira_issues_json)
{
        bool ret = false;
        autofree(gchar) *secret = NULL;
        autofree(gchar) *url = NULL;
        CURL *curl = NULL;
        CURLcode curl_status = CURLE_OK;
        struct curl_slist *headers = NULL;
        struct jira_data_t response;

        if (!is_initialized()) {
                return false;
        }
        if (is_empty(jira_json)) {
                fprintf(stderr, "Error: Missing json to search for JIRA issues\n");
                return false;
        }
        if (is_empty(g_jira_cfg->url)) {
                fprintf(stderr, "Error: Missing require JIRA server URL\n");
                return false;
        }
        if (!get_jira_secret(&secret)) {
                return false;
        }
        url = g_strdup_printf("%s/search", g_jira_cfg->url);
        if (url == NULL) {
                curl_global_cleanup();
                fprintf(stderr, "Error: Out of memory for searching issues\n");
                return false;
        }
        curl = curl_easy_init();
        if (!curl) {
                curl_global_cleanup();
                fprintf(stderr, "Error: Curl failed to initialize for searching issues\n");
                return false;
        }
        response.data = g_malloc(1);
        if (response.data == NULL) {
                curl_global_cleanup();
                fprintf(stderr, "Error: Out of memory\n");
                return false;
        }
        response.size = 0;
        headers = curl_slist_append(NULL, "Accept: application/json");
        headers = curl_slist_append(headers, "Content-Type: application/json");
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
        curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcurl-agent/1.0");
        curl_easy_setopt(curl, CURLOPT_URL, url);
        curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L);
        curl_easy_setopt(curl, CURLOPT_USERPWD, secret);
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, jira_json);
        curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)get_str_length(jira_json));
        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_jira_cb);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&response);
        if (g_jira_cfg->timeout_secs) {
                curl_easy_setopt(curl, CURLOPT_TIMEOUT, g_jira_cfg->timeout_secs);
        }
        if (g_jira_cfg->verbose) {
                curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
                fprintf(stderr, "%s\n", jira_json);
        }
        curl_status = curl_easy_perform(curl);
        if (curl_status != CURLE_OK) {
                fprintf(stderr, "Error: %s\n", curl_easy_strerror(curl_status));
                ret = false;
        } else {
                if (g_jira_cfg->verbose) {
                        fprintf(stderr, "%s\n", response.data);
                }
                ret = check_jira_response(response.data);
                if (ret) {
                        *jira_issues_json = g_strdup(response.data);
                }
        }
        g_free(response.data);
        curl_easy_cleanup(curl);
        curl_global_cleanup();
        return ret;
}

void set_password(const gchar *password)
{
        if ((g_jira_cfg != NULL) && (password != NULL)) {
                g_free(g_jira_cfg->password);
                g_jira_cfg->user = g_strdup(password);
        }
}

void set_timeout_secs(int timeout_secs)
{
        if ((g_jira_cfg != NULL) && (timeout_secs >= 0)) {
                g_jira_cfg->timeout_secs = timeout_secs;
        }
}

void set_url(const gchar *url)
{
        if ((g_jira_cfg != NULL) && (url != NULL)) {
                g_free(g_jira_cfg->url);
                g_jira_cfg->url = g_strdup(url);
        }
}

void set_user(const gchar *user)
{
        if ((g_jira_cfg != NULL) && (user != NULL)) {
                g_free(g_jira_cfg->user);
                g_jira_cfg->user = g_strdup(user);
        }
}

void set_verbose(bool verbose_flag)
{
        if (g_jira_cfg != NULL) {
                g_jira_cfg->verbose = verbose_flag;
        }
}

void show_jira_issues(const GSList *jira_issues)
{
        const GSList *iter = NULL;
        struct jira_issue_t *jira_issue = NULL;
        int jira_issues_count = get_jira_issues_count(jira_issues);

        if (!jira_issues_count) {
                fprintf(stderr, "No JIRA issues found\n");
                return;
        }
        iter = jira_issues;
        while (iter) {
                jira_issue = iter->data;
                fprintf(stderr, "JIRA Key:    %s\n", jira_issue->key);
                fprintf(stderr, "Summary:     %s\n", jira_issue->summary);
                fprintf(stderr, "Status:      %s\n", jira_issue->status);
                fprintf(stderr, "Resolution:  %s\n", jira_issue->resolution);
                fprintf(stderr, "Description: %s\n\n", jira_issue->description);
                iter = iter->next;
        }
        fprintf(stderr, "Total JIRA Issues: %u\n", jira_issues_count);
}

static size_t write_jira_cb(void *ptr, size_t size, size_t memb, void *data)
{
        size_t new_size = size * memb;
        struct jira_data_t *mem = (struct jira_data_t *)data;

        mem->data = realloc(mem->data, mem->size + new_size + 1);
        if (mem->data == NULL) {
                fprintf(stderr, "Error: Out of memory\n");
                return 0;
        }
        memcpy(&(mem->data[mem->size]), ptr, new_size);
        mem->size += new_size;
        mem->data[mem->size] = 0;
        return new_size;
}

/*
 * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
 *
 * Local variables:
 * c-basic-offset: 8
 * tab-width: 8
 * indent-tabs-mode: nil
 * End:
 *
 * vi: set shiftwidth=8 tabstop=8 expandtab:
 * :indentSize=8:tabSize=8:noTabs=true:
 */
